package entity;

import java.util.ArrayList;
import java.util.ListIterator;
import java.util.Stack;

import util.Constants;
import util.SelectionMode;
import dao.BoardMementoDAO;
import dao.WordDAO;

/**
 * This is the Board class.
 */
public class Board {
	private ArrayList<Word> unprotectedWords;
	private ArrayList<Word> protectedWords;
	private ArrayList<Poem> poems;
	private Stack<Move> moves;
	private Stack<Move> undoneMoves;
	private boolean positionsInitialized;

	/**
	 * Constructor of Board.
	 */
	public Board() {
		BoardMementoDAO dao = new BoardMementoDAO();
		BoardMemento bm = dao.findBoardMementoByFileName(BoardMementoDAO.FILE_NAME);

		if(bm != null){
			this.loadBoardMemento(bm);

			setPositionsInitialized(true);
		}else{
			System.out.println("Creating new save state.");
			
			this.unprotectedWords = new ArrayList<Word>();
			this.protectedWords = new ArrayList<Word>();
			this.poems = new ArrayList<Poem>();

			WordDAO wd = new WordDAO();

			unprotectedWords.addAll(wd.findWordsByFileName(WordDAO.FILE_NAME));

			setPositionsInitialized(false);
		}
		
		flushMoves();
	}

	/**
	 * Protect a word.
	 * This word would be added to protectedWords and removed from unprotectedWords.
	 */
	public void protectWord(Word word){
		if(!protectedWords.contains(word)){
			protectedWords.add(word);
			unprotectedWords.remove(word);
		}
	}

	/**
	 * Release a word.
	 * This word would be removed from protectedWords and added to unprotectedWords.
	 */
	public void releaseWord(Word word){
		if(!unprotectedWords.contains(word)){
			protectedWords.remove(word);
			unprotectedWords.add(word);
		}
	}

	/**
	 * Set a poem to the board.
	 */
	public void setPoem(Poem p){
		this.poems.add(p);
	}

	/**
	 * Release a word, row or poem from board.
	 * The word(s) would be removed from protectedWords and added to unprotectedWords.
	 */
	public void release(Movable m){
		if(m instanceof Poem){
			Poem p = (Poem) m;
			
			for(Row r : p.getRows()){
				for (Word w : r.getWords()){
					releaseWord(w);
					randomizeUnprotectedWord(w);
				}
			}
			
			poems.remove(p);
		}else if(m instanceof Row){
			Row r = (Row) m;
			
			for (Word w : r.getWords()){
				releaseWord(w);
				randomizeUnprotectedWord(w);
			}
			
			poems.remove(poemThatContains(r));
		}else if(m instanceof Word){
			Word w = (Word) m;
			
			releaseWord(w);
		}
	}

	/**
	 * Load protectedWords, unprotectedWords and poems to board.
	 */
	public void loadBoardMemento(BoardMemento bm){
		this.protectedWords = bm.getProtectedWords();
		this.unprotectedWords = bm.getUnprotectedWords();
		this.poems = bm.getPoems();
	}

	/**
	 * Randomize unprotectedWords.
	 */
	public void randomizeUnprotectedWords(){
		for(Word w : unprotectedWords){
			randomizeUnprotectedWord(w);
		}
	}
	
	/**
	 * Randomize a unprotected word.
	 */
	public void randomizeUnprotectedWord(Word w){
		w.setX((int) Math.round(Math.random() * (Constants.BOARD_WIDTH - w.width)));
		w.setY(Constants.PROTECTED_AREA_HEIGHT + (int) Math.round(Math.random() * (Constants.UNPROTECTED_AREA_HEIGHT - w.height)));
	}

	/**
	 * 
	 */
	public Movable getMovableAt(int x, int y, SelectionMode s){
		Movable movable = null;
		Word hoveredWord = null;

		if(y < Constants.PROTECTED_AREA_HEIGHT){
			for(Word w : protectedWords){
				if(w.contains(x, y)){
					hoveredWord = w;
					break;
				}
			}

			if(s == SelectionMode.DEFAULT || s == SelectionMode.ONLY_ROWS){
				Poem hoveredPoem = poemThatContains(hoveredWord);
				Row hoveredRow = hoveredPoem != null ? hoveredPoem.rowThatContains(hoveredWord) : null;

				if(s == SelectionMode.DEFAULT){
					movable = hoveredPoem != null ? hoveredPoem : hoveredWord;
				}else if(s == SelectionMode.ONLY_ROWS){
					movable = hoveredRow != null ? hoveredRow : hoveredWord;
				}
			}else if(s == SelectionMode.ONLY_WORDS){
				movable = hoveredWord;
			}
		}else{
			ListIterator<Word> it = unprotectedWords.listIterator(unprotectedWords.size());

			while(it.hasPrevious()){
				Word w = it.previous();

				if(w.contains(x, y)){
					hoveredWord = w;
					movable = w;
					break;
				}
			}
		}

		return movable;
	}

	/**
	 * Bring the word, row or poem been selected to the front.
	 */
	public void bringToFront(Movable m){
		if(m instanceof Word){
			Word w = (Word) m;
			
			bringWordToFront(w);
		}else if(m instanceof Row){
			for(Word w : ((Row) m).getWords()){
				bringWordToFront(w);
			}
		}else if(m instanceof Poem){
			for(Row r : ((Poem) m).getRows()){
				for(Word w : r.getWords()){
					bringWordToFront(w);
				}
			}
		}
	}
	
	/**
	 * Bring a word to the front.
	 */
	public void bringWordToFront(Word w){
		if(unprotectedWords.contains(w)){
			unprotectedWords.remove(w);
			unprotectedWords.add(w);
		}else if(protectedWords.contains(w)){
			protectedWords.remove(w);
			protectedWords.add(w);
		}
	}

	/**
	 * Get the poem that contains a certain word.
	 */
	public Poem poemThatContains(Movable m){
		if(m instanceof Word){
			for(Poem p : poems){
				if(p.contains((Word) m)){
					return p;
				}
			}
		}else if(m instanceof Row){
			for(Poem p : poems){
				if(p.contains((Row) m)){
					return p;
				}
			}
		}

		return null;
	}
	
	/**
	 * Return the words that belongs to a specific type.
	 */
	public ArrayList<Word> getUnprotectedWordsByType(WordType wt){
		ArrayList<Word> result = new ArrayList<Word>();
		
		for(Word w : unprotectedWords){
			if(wt == WordType.ANY || w.getWordType() == wt){
				result.add(w);
			}
		}
		
		return result;
	}
	
	/**
	 * Return the words that has a specific value.
	 */
	public ArrayList<Word> getUnprotectedWordsByValue(String value){
		ArrayList<Word> result = new ArrayList<Word>();
		
		for(Word w : unprotectedWords){
			if(w.getValue().equals(value)){
				result.add(w);
			}
		}
		
		return result;
	}
	
	/**
	 * Return the words belongs a specific type.
	 */
	public ArrayList<Word> getUnprotectedWordsByValueAndType(WordType wt, String value){
		ArrayList<Word> result = new ArrayList<Word>();
		
		for(Word w : unprotectedWords){
			if(w.getValue().equals(value) && (wt == WordType.ANY || w.getWordType() == wt)){
				result.add(w);
			}
		}
		
		return result;
	}

	/**
	 * Return unprotected words.
	 */
	public ArrayList<Word> getUnprotectedWords() {
		return unprotectedWords;
	}

	/**
	 * Get poems on the board.
	 */
	public ArrayList<Poem> getPoems(){
		return poems;
	}

	/**
	 * Return protected words.
	 */
	public ArrayList<Word> getProtectedWords() {
		return protectedWords;
	}

	/**
	 * Check whether the poem has been initialized its position.
	 */
	public boolean isPositionsInitialized() {
		return positionsInitialized;
	}

	/**
	 * Initialized the position for a poem.
	 */
	public void setPositionsInitialized(boolean positionsInitialized) {
		this.positionsInitialized = positionsInitialized;
	}
	
	/**
	 * Undo the last move.
	 */
	public Move undo(){
		Move move = null;
		
		if(moves.size() > 0){
			move = moves.pop();
			undoneMoves.push(move);
		}
		
		return move;
	}
	
	/**
	 * Redo the last undo move. 
	 */
	public Move redo(){
		Move move = null;
		
		if(undoneMoves.size() > 0){
			move = undoneMoves.pop();
			moves.push(move);
		}
		
		return move;
	}
	
	/**
	 * Store the last move to moves stack. 
	 */
	public void addMove(Move move){
		moves.push(move);
		
		if(undoneMoves.size() > 0){
			undoneMoves = new Stack<Move>();
		}
	}
	
	/**
	 * Apply Move stacks for a new Board.
	 */
	public void flushMoves(){
		this.moves = new Stack<Move>();
		this.undoneMoves = new Stack<Move>();
	}
	
	/**
	 * Remove a word from the Board.
	 */
	public void removeWord(Word w){
		protectedWords.remove(w);
		unprotectedWords.remove(w);
	}
	
	/**
	 * Add a word to the Board. 
	 */
	public void addWord(Word w){
		if(w.getY() < Constants.PROTECTED_AREA_HEIGHT){
			protectedWords.add(w);
		}else{
			unprotectedWords.add(w);
			randomizeUnprotectedWord(w);
		}
	}
}

